package utils

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"reflect"
	"strings"
)

func isFieldPrivate(fieldName string) bool {
	return strings.ToLower(fieldName[:1]) == fieldName[:1]
}

func readData(r io.Reader, fv reflect.Value, byteOrder binary.ByteOrder) error {
	switch fv.Kind() {
	case reflect.Bool, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64, reflect.Float32, reflect.Float64:
		err := binary.Read(r, byteOrder, fv.Addr().Interface())
		if err != nil {
			return err
		}
	case reflect.String:
		var length uint32
		err := binary.Read(r, byteOrder, &length)
		if err != nil {
			return err
		}
		str := make([]byte, length)
		_, err = io.ReadFull(r, str)
		if err != nil {
			return err
		}
		fv.SetString(string(str))
	case reflect.Array:
		var length uint32
		err := binary.Read(r, byteOrder, &length)
		if err != nil {
			return err
		}
		for i := 0; i < int(length); i++ {
			tmpValue := fv.Index(i)
			err := readData(r, tmpValue, byteOrder)
			if err != nil {
				return err
			}
		}
	case reflect.Slice:
		var length uint32
		err := binary.Read(r, byteOrder, &length)
		if err != nil {
			return err
		}
		fv.Grow(int(length))
		fv.SetLen(int(length))
		for i := 0; i < int(length); i++ {
			tmpValue := fv.Index(i)
			err := readData(r, tmpValue, byteOrder)
			if err != nil {
				return err
			}
		}
	case reflect.Map:
		var length uint32
		err := binary.Read(r, byteOrder, &length)
		if err != nil {
			return err
		}
		mapType := fv.Type()
		tmpFile := reflect.MakeMap(mapType)
		fv.Set(tmpFile)
		keyType := mapType.Key()
		valueType := mapType.Elem()
		for i := 0; i < int(length); i++ {
			key := reflect.New(keyType).Elem()
			err := readData(r, key, byteOrder)
			if err != nil {
				return err
			}
			value := reflect.New(valueType).Elem()
			err = readData(r, value, byteOrder)
			if err != nil {
				return err
			}
			fv.SetMapIndex(key, value)
		}
	case reflect.Struct:
		if fv.CanSet() {
			return readStruct(r, fv, byteOrder)
		}
	default:
		return fmt.Errorf("unsupported field type: %v", fv.Kind())
	}
	return nil
}

func readStruct(r io.Reader, rv reflect.Value, byteOrder binary.ByteOrder) error {
	vt := rv.Type()
	if vt.Kind() != reflect.Struct {
		return fmt.Errorf("rv must be a struct")
	}

	for i := 0; i < rv.NumField(); i++ {
		fv := rv.Field(i)
		ft := vt.Field(i)
		if isFieldPrivate(ft.Name) {
			continue
		}

		err := readData(r, fv, byteOrder)
		if err != nil {
			return err
		}
	}
	return nil
}

func ReadStruct(r io.Reader, v interface{}, byteOrder binary.ByteOrder) error {
	val := reflect.ValueOf(v)
	if val.Kind() != reflect.Ptr {
		return errors.New("v must be a pointer to a struct")
	}
	return readStruct(r, val.Elem(), byteOrder)
}

func writeData(buf *bytes.Buffer, fv reflect.Value, byteOrder binary.ByteOrder) error {
	switch fv.Kind() {
	case reflect.Int8, reflect.Uint8:
		err := binary.Write(buf, byteOrder, int8(fv.Int()))
		if err != nil {
			return err
		}
	case reflect.Int16, reflect.Uint16:
		err := binary.Write(buf, byteOrder, int16(fv.Int()))
		if err != nil {
			return err
		}
	case reflect.Int32, reflect.Uint32:
		err := binary.Write(buf, byteOrder, int32(fv.Int()))
		if err != nil {
			return err
		}
	case reflect.Int64, reflect.Uint64:
		err := binary.Write(buf, byteOrder, int64(fv.Int()))
		if err != nil {
			return err
		}

	case reflect.Float32:
		err := binary.Write(buf, byteOrder, float32(fv.Float()))
		if err != nil {
			return err
		}
	case reflect.Float64:
		err := binary.Write(buf, byteOrder, float64(fv.Float()))
		if err != nil {
			return err
		}
	case reflect.Bool:
		var b [1]byte
		if fv.Bool() {
			b[0] = 1
		}
		_, err := buf.Write(b[:])
		if err != nil {
			return err
		}
	case reflect.String:
		str := fv.String()
		length := int32(len(str))
		err := binary.Write(buf, byteOrder, length)
		if err != nil {
			return err
		}
		_, err = buf.WriteString(str)
		if err != nil {
			return err
		}
	case reflect.Array, reflect.Slice:
		length := int32(fv.Len())
		err := binary.Write(buf, byteOrder, length)
		if err != nil {
			return err
		}
		for i := 0; i < int(length); i++ {
			tmpValue := fv.Index(i)
			err := writeData(buf, tmpValue, byteOrder)
			if err != nil {
				return err
			}
		}
	case reflect.Map:
		length := int32(fv.Len())
		err := binary.Write(buf, byteOrder, length)
		if err != nil {
			return err
		}
		for _, lvKey := range fv.MapKeys() {
			lvData := fv.MapIndex(lvKey)
			err := writeData(buf, lvKey, byteOrder)
			if err != nil {
				return err
			}
			err = writeData(buf, lvData, byteOrder)
			if err != nil {
				return err
			}
		}
	case reflect.Struct:
		err := writeStruct(buf, fv, byteOrder)
		if err != nil {
			return err
		}
	default:
		return fmt.Errorf("unsupported field type: %v", fv.Kind())
	}
	return nil
}

func writeStruct(buf *bytes.Buffer, rv reflect.Value, byteOrder binary.ByteOrder) error {
	rt := rv.Type()
	if rv.Kind() != reflect.Struct {
		return fmt.Errorf("expected a struct, got %v", rv.Kind())
	}
	for i := 0; i < rv.NumField(); i++ {
		fv := rv.Field(i)
		ft := rt.Field(i)
		if isFieldPrivate(ft.Name) {
			continue
		}

		err := writeData(buf, fv, byteOrder)
		if err != nil {
			return err
		}
	}
	return nil
}

func WriteStruct(buf *bytes.Buffer, v interface{}, byteOrder binary.ByteOrder) error {
	rv := reflect.ValueOf(v)
	return writeStruct(buf, rv, byteOrder)
}
